package com.xm.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ListView;

public class ThirdListView extends ListView {
    private static final String TAG = "ThirdListView";
    private static final int ANIMATION_DURATION = 200;
    private static final int SNAPSHOT_ALPHA = (int)(255 * 0.6);
    private static final float SCROLL_BOUND = 1 / 4.0f;
    private static final int SCROLL_SPEED_MAX = 16;

    public interface OnOrderChangedListener {
        public void OnOrderChanged(int from, int to);
    }

    private OnOrderChangedListener mOnOrderChangedListener;
    private OnTouchListener mOnTouchListener;
    private boolean mInterceptTouchForSorting;
    private int mDraggingFrom = -1;
    private int mDraggingTo = -1;
    private int mDraggingY;
    private int mOffsetYInDraggingItem;
    private View mDragView;

    private int mDraggingItemWidth;
    private int mDraggingItemHeight;

    private BitmapDrawable mSnapshot;
    private Drawable mSnapshotShadow;
    private int mSnapshotShadowPaddingTop;
    private int mSnapshotShadowPaddingBottom;

    private int mScrollBound;
    private int mScrollUpperBound;
    private int mScrollLowerBound;

    private int mItemUpperBound = -1;
    private Drawable mSnapshotBackgroundForOverUpperBound;

    public ThirdListView(Context context) {
        this(context, null);
    }

    public ThirdListView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.listViewStyle);
    }

    public ThirdListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private Animation createAnimation(int fromX, int toX, int fromY, int toY) {
        Animation result = new TranslateAnimation(fromX, toX, fromY, toY);
        result.setDuration(ANIMATION_DURATION);
        result.setFillAfter(true);
        return result;
    }

    public void setItemUpperBound(int upper, Drawable snapshotShadow) {
        mItemUpperBound = upper;
        mSnapshotBackgroundForOverUpperBound = snapshotShadow;
    }

    public void setOnOrderChangedListener(OnOrderChangedListener l) {
        mOnOrderChangedListener = l;
    }

    public OnTouchListener getListenerForStartingSort() {
        return mOnTouchListener;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mScrollBound = Math.max(1, (int) (h * SCROLL_BOUND));
        mScrollUpperBound = (int) mScrollBound;
        mScrollLowerBound = (int) (h - mScrollBound);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);

        if (mDraggingFrom >= 0) {
            int offset = mDraggingY - mOffsetYInDraggingItem;

            // make sure dragging item below header views
            int index = getHeaderViewsCount();
            if (index < getFirstVisiblePosition() || index > getLastVisiblePosition()) {
                index = getFirstVisiblePosition();
            }
            index -= getFirstVisiblePosition();
            offset = Math.max(offset, getChildAt(index).getTop());

            // make sure dragging item is above footer views
            index = getCount() - 1 - getFooterViewsCount();
            if (index < getFirstVisiblePosition() || index > getLastVisiblePosition()) {
                index = getLastVisiblePosition();
            }
            index -= getFirstVisiblePosition();
            offset = Math.min(offset, getChildAt(index).getBottom() - mDraggingItemHeight);

            canvas.translate(0, offset);
            if (mSnapshotShadow != null) {
                mSnapshotShadow.draw(canvas);
            }
            if (mSnapshot != null) {
                mSnapshot.draw(canvas);
            }
            if (mSnapshotBackgroundForOverUpperBound != null && mDraggingTo < mItemUpperBound) {
                mSnapshotBackgroundForOverUpperBound.draw(canvas);
            }
            canvas.translate(-0, -offset);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mOnOrderChangedListener != null) {
                        int position = getHittenItemPosition(event);
                        if (position >= 0) {
                            // initialize sorting states
                            mDraggingFrom = position;
                            mDraggingTo = position;

                            // get size of the item being dragged
                            View view = getChildAt(position - getFirstVisiblePosition());
                            mDraggingItemWidth = view.getWidth();
                            mDraggingItemHeight = view.getHeight();

                            // get the offset for drawing snapshot
                            ThirdListView.this.getLocationOnScreen(mTmpLocation);
                            mDraggingY = (int)(event.getRawY()) - mTmpLocation[1];
                            mOffsetYInDraggingItem = mDraggingY - view.getTop();

                        }
                }
                return false;
        }
        if (mInterceptTouchForSorting) {
            onTouchEvent(event);
            return true;
        }

        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (!mInterceptTouchForSorting) {
            return super.onTouchEvent(ev);
        }

        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_MOVE:
            int y = (int) ev.getY();
            if (!mInterceptTouchForSorting && y == mDraggingY) break;

            int position = getHittenItemPosition(ev);
            if (position < getHeaderViewsCount() || position > getCount() - getFooterViewsCount()) {
                position = mDraggingTo;
            }
            updateDraggingToPisition(position);

            // update the offset for drawing snapshot
            mDraggingY = y;
            invalidate();

            // scroll the list if the item is dragged to the top or bottom of the list
            int delta = 0;
            if (y > mScrollLowerBound) {
                // scroll the list up a bit
                delta = SCROLL_SPEED_MAX * (mScrollLowerBound - y) / mScrollBound;
            } else if (y < mScrollUpperBound) {
                // scroll the list down a bit
                delta = SCROLL_SPEED_MAX * (mScrollUpperBound - y) / mScrollBound;
            }
            if (delta != 0) {
                View v = getChildAt(position - getFirstVisiblePosition());
                if (v != null) {
                    setSelectionFromTop(position, v.getTop() + delta);
                }
            }
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (mDraggingFrom >= 0) {
                if (mOnOrderChangedListener != null && mDraggingFrom != mDraggingTo
                    && mDraggingTo >= 0) {
                    mOnOrderChangedListener.OnOrderChanged(
                            mDraggingFrom - getHeaderViewsCount(),
                            mDraggingTo - getHeaderViewsCount());
                        setViewAnimation(mDragView, createAnimation(30, 0,
                                (mDraggingTo - mDraggingFrom - getFirstVisiblePosition()) * mDraggingItemHeight + 15,
                                (mDraggingTo - mDraggingFrom - getFirstVisiblePosition()) * mDraggingItemHeight));
                    Log.d(TAG, "a: " + getFirstVisiblePosition());
                }
                else {
                    setViewAnimationByPisition(mDraggingFrom, createAnimation(30, 0, 15, 0));
                    Log.d(TAG, "b");
                }
            }

            stopDragging();
            mInterceptTouchForSorting = false;
            mDraggingFrom = -1;
            mDraggingTo = -1;
            invalidate();
            break;
        }

        return true;
    }

    private int[] mTmpLocation = new int[2];
    private int getHittenItemPosition(MotionEvent event) {
        float x = event.getRawX();
        float y = event.getRawY();
        int firstPosition = getFirstVisiblePosition();

        for (int i = getLastVisiblePosition(); i >= firstPosition; i--) {
            View view = getChildAt(i - firstPosition);
            if (view != null) {
                view.getLocationOnScreen(mTmpLocation);
                if (mTmpLocation[0] <= x && mTmpLocation[0] + view.getWidth() >= x
                    && mTmpLocation[1] <= y && mTmpLocation[1] + view.getHeight() >= y) {
                    return i;
                }
            }
        }
        return -1;
    }

    private void updateDraggingToPisition(int draggingTo) {
        if (draggingTo == mDraggingTo || draggingTo < 0) return;

        Log.d(TAG, "sort item from " + mDraggingFrom + " To " + draggingTo);

        // set reverse animations for items which is in sorting range previously but is out now
        // for case: ...ddd---...
        if (mDraggingFrom < Math.max(mDraggingTo, draggingTo)) {
            while (mDraggingTo > draggingTo && mDraggingTo > mDraggingFrom) {
                Log.d(TAG, "item " + mDraggingTo + " set move down reverse animation");
                setViewAnimationByPisition(mDraggingTo--, createAnimation(0, 0, -mDraggingItemHeight, 0));
            }
        }

        // for case: ...---uuu...
        if (mDraggingFrom > Math.min(mDraggingTo, draggingTo)) {
            while (mDraggingTo < draggingTo && mDraggingTo < mDraggingFrom) {
                Log.d(TAG, "item " + mDraggingTo + " set move up reverse animation");
                setViewAnimationByPisition(mDraggingTo++, createAnimation(0, 0, mDraggingItemHeight, 0));
            }
        }

        // set animations for items which is in sorting range now but is out previously
        // for case: ...ddd+++...
        if (mDraggingFrom < Math.max(mDraggingTo, draggingTo)) {
            while (mDraggingTo < draggingTo) {
                setViewAnimationByPisition(++mDraggingTo, createAnimation(0, 0, 0, -mDraggingItemHeight));
                Log.d(TAG, "item " + mDraggingTo + " set move up animation");
            }
        }

        // for case: ...+++uuu...
        if (mDraggingFrom > Math.min(mDraggingTo, draggingTo)) {
            while (mDraggingTo > draggingTo) {
                setViewAnimationByPisition(--mDraggingTo, createAnimation(0, 0, 0, mDraggingItemHeight));
                Log.d(TAG, "item " + mDraggingTo + " set move down animation");
            }
        }
    }

    private void setViewAnimationByPisition(int position, Animation animation) {
        View item = getChildAt(position - getFirstVisiblePosition());
        setViewAnimation(item, animation);
    }

    private void setViewAnimation(View view, Animation animation) {
        if (view == null) return;

        if (animation != null) {
            view.startAnimation(animation);
        }
        else {
            view.clearAnimation();
        }
    }

    public void startDragging(View view, int y) {
        mDragView = view;
        stopDragging();
        mInterceptTouchForSorting = true;
        view.setDrawingCacheEnabled(true);
        Bitmap snapshot = Bitmap.createBitmap(view.getDrawingCache());
        mSnapshot = new BitmapDrawable(getResources(), snapshot);
        mSnapshot.setBounds(
                view.getLeft(),
                0,
                view.getRight(),
                mDraggingItemHeight);

        if (mSnapshotBackgroundForOverUpperBound != null) {
            mSnapshotBackgroundForOverUpperBound.setAlpha(SNAPSHOT_ALPHA);
            mSnapshotBackgroundForOverUpperBound.setBounds(
                    view.getLeft(),
                    0,
                    view.getRight(),
                    mDraggingItemHeight);
        }
        mSnapshotShadow = mSnapshot;
        mSnapshotShadow.setColorFilter(Color.GRAY, PorterDuff.Mode.DST_ATOP);
        // setup snapshot shadow
        mSnapshotShadow.setBounds(
                view.getLeft() + 30,
                -mSnapshotShadowPaddingTop + 15,
                view.getRight(),
                mDraggingItemHeight + mSnapshotShadowPaddingBottom + 15);

        // hide the actual item being dragged
        view.startAnimation(createAnimation(mDraggingItemWidth, mDraggingItemWidth, 0, 0));
    }

    private void stopDragging() {
        if (mSnapshot != null) {
            mSnapshot.invalidateSelf();
            mSnapshot = null;
        }
        if (mSnapshotShadow != null) {
            mSnapshotShadow.invalidateSelf();
            mSnapshotShadow = null;
        }
//        if (mDragView != null) {
//            mDragView.destroyDrawingCache();
//            mDragView = null;
//        }
    }
}